﻿using System;
using System.Collections.Generic;
using System.Linq;
using Airport;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace Airport
{
    [Serializable]
    [DataContract(IsReference=true)]    
    [KnownType(typeof(Point))]   
    public class Node : Airport.INode, IEquatable<Node>/*, INotifyPropertyChanged*/
    {
        /// <note>
        /// we may or may not want a point data structure. it can make drawing easier for alec
        /// </note>
        private Point _coordinate;
        private int _xPos, _yPos, _nodeID;
        private float _latitude, _longitude;
        private List<Node> _neighbors;
        private bool _isHoldBar;

        public Node() 
        {
            _coordinate = new Point(-1, -1);
            _xPos = -1;
            _yPos = -1;
            _nodeID = -1;
            _isHoldBar = false;
            _neighbors = new List<Node>();
        }

        public Node(int x, int y)
            : this(-1, x, y)
        {
        }

        public Node(int id, int x, int y)
        {
            _coordinate = new Point(x, y);
            _xPos = x;
            _yPos = y;
            _nodeID = id;
            _isHoldBar = false;
            _neighbors = new List<Node>(); // Should this be null?
            // Should we set the lat and long explicitly?
        }

        // Copy constructor
        public Node(Node other)
        {
            _coordinate = new Point(other._coordinate);
            _xPos = other._xPos;
            _yPos = other._yPos;
            _nodeID = other._nodeID;
            _isHoldBar = other._isHoldBar;
            _neighbors = new List<Node>(other._neighbors);
        }

        /// <summary>
        /// getters and setters
        /// </summary>
        /// 

        [DataMember]
        public int NodeId
        {
            get
            {
                return _nodeID;
            }

            set
            {
                if (_nodeID != value)
                {
                    _nodeID = value;
                }
            }
        }

        [DataMember]
        public int XPos
        {
            get
            {
                return _xPos;
            }

            set
            {
                if (_xPos != value)
                {
                    int oldX = _xPos;
                    _xPos = value;
                    //PropertyChanged(this, new PropertyChangedEventArgs("XPos"));
                    
                    //NodeChanged(this, new NodeChangedEventArgs(oldX, _xPos));
                }
            }
        }
        
        [DataMember]
        public int YPos
        {
            get
            {
                return _yPos;
            }

            set
            {
                if (_yPos != value)
                {
                    int oldY = _yPos;
                    _yPos = value;
                    //PropertyChanged(this, new PropertyChangedEventArgs("YPos"));
                    
                    //NodeChanged(this, new NodeChangedEventArgs(_xPos, oldY));
                }
            }
        }

        //public event PropertyChangedEventHandler PropertyChanged;
        //public delegate void NodeChangedEventHandler(object sender, NodeChangedEventArgs e);
        [NonSerialized]
        private EventHandler<NodeChangedEventArgs> _nodeChangedEventHandler;
        public event EventHandler<NodeChangedEventArgs> NodeChangedEventHandler
        {
            add
            {
                _nodeChangedEventHandler += value;
            }

            remove
            {
                _nodeChangedEventHandler -= value;
            }
        }
        //public event NodeChangedEventHandler NodeChanged;

        [DataMember]
        public float Latitude
        {
            get { return _latitude; }
            set
            {
                if (_latitude != value)
                {
                    _latitude = value;
                }
            }
        }

        [DataMember]
        public float Longitude
        {
            get { return _longitude; }
            set
            {
                if (_longitude != value)
                {
                    _longitude = value;
                }
            }
        }

        [DataMember]
        public Point Coordinate
        {
            get { return _coordinate; }
            set
            {
                if (_coordinate != value)
                {
                    _coordinate = value;
                }
            }
        }

        //[DataMember]
        public List<Node> Neighbors
        {
            get
            {
                return _neighbors;
            }

            set
            {
                if (_neighbors != value)
                {
                    _neighbors = value;
                }
            }
        }

        [DataMember]
        public bool IsHoldBar
        {
            get
            {
                return _isHoldBar;
            }

            set
            {
                _isHoldBar = value;
            }
        }

        /// <summary>
        /// Returns the straight-line distance to another Node.
        /// </summary>
        /// <param name="other"></param>
        /// <returns>Straight-line distance to the given Node.</returns>
        public double DistanceTo(Node other)
        {
            int xDifference, yDifference;
            double xSquared, ySquared;

            xDifference = this._xPos - other._xPos;
            yDifference = this._yPos - other._yPos;

            xSquared = xDifference * xDifference;
            ySquared = yDifference * yDifference;

            return Math.Sqrt(xSquared + ySquared);            
        }

        public override string ToString()
        {
            string str = "(" + _xPos + ", " + _yPos + ")";
            return str;
        }

        public bool Equals(Node other)
        {
            if (other == null)
            {
                return false;
            }

            return (_xPos == other._xPos) && (_yPos == other._yPos);
        }

        public override bool Equals(Object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (obj.GetType() != typeof(Node))
            {
                return false;
            }

            return _xPos == ((Node)obj)._xPos && _yPos == ((Node)obj)._yPos;
        }

        /// <summary>
        /// Hashing method for Node type 
        /// </summary>
        /// <returns>A unique hash code for all Nodes less than (2^16, 2^16). Good luck getting a monitor with a resolution anywhere near that.</returns>
        public override int GetHashCode()
        {
            return (_xPos << 16) | (_yPos & 0xFFFF);
        }

        public static bool operator==(Node a, Node b)
        {
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            if (((object)a == null) || ((object)b == null))
            {
                return false;
            }

            return (a._xPos == b._xPos) && (a._yPos == b._yPos);
        }

        public static bool operator !=(Node a, Node b)
        {
            return !(a == b);
        }
    }

    [Serializable]
    public class NodeChangedEventArgs : EventArgs
    {
        private int _oldX;
        private int _oldY;

        public NodeChangedEventArgs(int oldX, int oldY)
        {
            _oldX = oldX;
            _oldY = oldY;
        }

        public int OldX
        {
            get { return _oldX; }
        }

        public int OldY
        {
            get { return _oldY; }
        }
    }

    [Serializable]
    public class NodeEqualityComparer : EqualityComparer<Node>
    {
        public override bool Equals(Node x, Node y)
        {
            if (((object)x == null) || ((object)y == null))
            {
                return false;
            }

            return x.Equals(y);
        }

        public override int  GetHashCode(Node obj)
        {
            return obj.GetHashCode();
        }
        
    }
    
}